昨天講了在各個資料庫中都通用的權限管理,而今天要則是要談談所有 SQL 的頭號公敵:SQL injection
雖然大部分人都已經知道 SQL injection 的原理了,不過這邊還是再簡單說明一下,所謂的 SQL injection 就是在 SQL 裡面插入一些怪怪的東西,藉以達到攻擊的效果
譬如說這是一個用 Node.js 寫成的 API Server,使用者輸入帳號密碼後,程式就會用使用者輸入的帳號密碼組合出 SQL statement 到資料庫中撈看看有沒有對應的使用者
const express = require("express")
const app = express()
app.post('/api/login', (req, res) => {
// 使用者輸入的帳號密碼
const { email, password } = req.body
// 用帳號密碼組出 sql statement
const statement = `SELECT * FROM users WHERE email='${email}' AND pass='${hash(password)}'`
const user = await db.query(statement);
// 有撈到東西就代表登入成功
if (user) {
res.json('login success')
}
})
但因為使用者可以自己決定要輸入什麼資料,只要懂一些 SQL 語法,就可以輸入 1' OR '1'='1' --
作為 email,然後把整個 SQL statement 變成 SELECT * FROM users WHERE email='1' OR '1'='1'
,讓他百分之百登入成功
要防範這類的攻擊,要做的第一件事情就是之前在 Day2 講的格式驗證,如果你有去檢查使用者輸入的 email 格式是不是怪怪的,那像 1' OR '1'='1' --
這種奇怪的輸入在第一時間就會馬上被拒絕掉,完全沒有通過的可能
雖然格式驗證並不是正規用來防禦 SQL injection 的做法,而且厲害的駭客還是可以想其他方法既通過驗證又達成 injection,但假如你的 API Server 真的有 SQL injection 的漏洞,那有一些基本的驗證也會讓這些漏洞更難被觸發,因此我個人建議格式驗證還是要有,反正安全性這種東西當然是越高越好,而且格式驗證的成本也滿低的不至於拖慢 Server 的速度
除了格式驗證之外,正規的做法是使用資料庫中 Prepared Statement 的功能,這樣就可以預先把變數的位置保留下來,等真正要進行 query 才把那些變數以「值」的方式丟給 SQL Server
如此一來資料庫在處理時就知道 email
跟 hash(password)
這兩個字串並不是語法而是單純的值,所以如果攻擊者使用 1' OR '1'='1' --
作為 email 想要進行 SQL injection,資料庫就不會把它當成語法來看待,而是會真的到 table 裡面去找找看有沒有誰的 email 是 1' OR '1'='1' --
(當然不會有XD),所以就不會找到任何使用者,也就不會讓攻擊者成功登入
app.post('/api/login', (req, res) => {
// 使用者輸入的帳號密碼
const { email, password } = req.body
// 用 ? 把變數的位置保留下來,在 query 時才把值給 SQL Server
const statement = `SELECT * FROM users WHERE email=? AND pass=?`
const user = await db.query(statement, [email, hash(password)]);
if (user) { res.json('login success') }
})
今天講了怎麼透過格式驗證及 Prepared Statement 來防範 SQL injection,雖然這已經是業界常識所以大家應該都已經很熟了XD,不過還是不厭其煩的幫大家複習一下
關於今天的內容有什麼問題歡迎在下方留言,沒問題的話明天就要來講講 NoSQL 的部分了~